package itpenguin.review.entities;

import itpenguin.review.util.SignitureUtil;

import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.jdo.annotations.Extension;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import com.google.appengine.api.datastore.Text;

/**
 * アイテム、レビュー情報
 * 
 * とりあえず、曖昧さが残る下記情報は、見送り
 * Creators, Languages （いずれも、いずれは入れたい）
 * @author Hayashi H.
 *
 */
@PersistenceCapable(detachable="true")
public class Item implements Serializable {

	private static final long serialVersionUID = 1L;
	private static final String EMPTY = "";
	private static final QName CATEGORY = new QName("Category");
	
	@PrimaryKey
	@Persistent
	private String itemId;
	
	public Item(String itemId) {
		this.itemId = itemId;
		this.asin = itemId;
	}
	
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private int lowestNewPrice; 
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private int lowestUsedPrice;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String swatchImageUrl;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String smallImageUrl;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String largeImageUrl;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String mediumImageUrl;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private Text detailPageUrl;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> varientImagesUrl = new ArrayList<String>();
	
	@Persistent
	private Date cachedDate;
	
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String binding;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String title;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String studio;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private Date releaseDate;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String publisher;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String manufacturer;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String label;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> actors = new ArrayList<String>();
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> artists = new ArrayList<String>();
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> directors = new ArrayList<String>();
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> formats = new ArrayList<String>();
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> authors = new ArrayList<String>();
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String aspectRatio;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String eSRBAgeRating;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String hardwarePlatform;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String operatingSystem;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String platform;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private int numberOfPages;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String productGroup;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private Date publicationDate;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private int runningTime;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> tracks = new ArrayList<String>();
	
	@Persistent
	private String asin;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private String reviewTitle;
	@Persistent
	private Date reviewDate;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private Text review;
	@Persistent
	private short score;
	@Persistent
	private Date lastModified;
	@Persistent
	@Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
	private List<String> relatedAsins = new ArrayList<String>();
	@Persistent
	private short numberOfStars;
	@Persistent
	private short reviewYear;
	
	public void updateItemInfo() throws XMLStreamException, IOException {

		//SystemProperty.environment.value() == SystemProperty.Environment.Value.Production
		XMLInputFactory factory = XMLInputFactory.newInstance();
		SortedMap<String, String> parms = new TreeMap<String, String>();
		parms.put("Operation", "ItemLookup");
		parms.put("ItemId", asin);
		parms.put("ResponseGroup", "Images,ItemAttributes,OfferSummary,Tracks");
        URL url = new URL(SignitureUtil.sign(parms));
		XMLEventReader r = factory.createXMLEventReader(url.openStream());
		
		parseFromXml(r);
		
		r.close();
		
		if (reviewTitle == null || reviewTitle.trim().isEmpty())
			reviewTitle = title;
		
		cachedDate = new Date();
	}
	
	private void parseFromXml(XMLEventReader r) throws XMLStreamException {
		detailPageUrl = null;

		boolean inItem = false;
		
		while (r.hasNext()) {
			XMLEvent e = r.nextEvent();
			if (e.isStartElement()) {
				StartElement element = (StartElement) e;
				String localPart = element.getName().getLocalPart();
				if (inItem) {
					if ("DetailPageURL".equals(localPart)) {
						Characters ch = (Characters) r.nextEvent();
						this.detailPageUrl = new Text(ch.getData());
					} else if ("ImageSets".equals(localPart)) {
						updateImageSets(r);
					} else if ("ItemAttributes".equals(localPart)) {
						updateItemAttributes(r);
					} else if ("OfferSummary".equals(localPart)) {
						updateOfferSummary(r);
					} else if ("Tracks".equals(localPart)) {
						updateTracks(r);
					}
				} else {
					if ("Item".equals(localPart)) {
						inItem = true;
					} else if ("Error".equals(localPart)) {
						r.nextEvent();
						StringBuilder sb = new StringBuilder();
						Characters ch = (Characters) r.nextEvent();
						sb.append(ch.getData());
						sb.append(":");
						r.nextEvent();
						r.nextEvent();
						ch = (Characters) r.nextEvent();
						sb.append(ch.getData());
						throw new XMLStreamException(sb.toString());
					}
				}
			} else if (e.isEndElement()) {
				EndElement element = (EndElement)e;
				String localPart = element.getName().getLocalPart();
				if (inItem && "Item".equals(localPart)) {
					inItem = false;
				}
			}
		}
		
	}
	
	private enum ImageSize{Swatch, Small, Medium, Large, Others};
	private void updateImageSets(XMLEventReader r) throws XMLStreamException {

		// initialize
		swatchImageUrl = null;
		smallImageUrl = null;
		largeImageUrl = null;
		mediumImageUrl = null;
		varientImagesUrl.clear();
		
		boolean primaryImage = false;
		boolean getUrl = false;
		ImageSize size = ImageSize.Others;
		
		while (r.hasNext()) {
			XMLEvent e = r.nextEvent();
			if (e.isStartElement()) {
				StartElement element = (StartElement) e;
				String localPart = element.getName().getLocalPart();
				if ("ImageSet".equals(localPart)) {
					primaryImage = "primary".equals(element.getAttributeByName(CATEGORY).getValue());
				} else if ("SwatchImage".equals(localPart)) {
					size = ImageSize.Swatch;
				} else if ("SmallImage".equals(localPart)) {
					size = ImageSize.Small;
				} else if ("MediumImage".equals(localPart)) {
					size = ImageSize.Medium;
				} else if ("LargeImage".equals(localPart)) {
					size = ImageSize.Large;
				} else if ("URL".equals(localPart)) {
					getUrl = true;
				}
			} else if (e.isCharacters()) {
				if (getUrl) {
					Characters ch = (Characters) e;
					if (primaryImage) {
						if (size == ImageSize.Swatch)
							this.swatchImageUrl = ch.getData();
						else if (size == ImageSize.Small)
							this.smallImageUrl = ch.getData();
						else if (size == ImageSize.Medium)
							this.mediumImageUrl = ch.getData();
						else if (size == ImageSize.Large)
							this.largeImageUrl = ch.getData();
					} else {
						if (size == ImageSize.Medium)
						this.varientImagesUrl.add(ch.getData());
					}
					getUrl = false;
				}
			} else if (e.isEndElement()) {
				EndElement element = (EndElement)e;
				String localPart = element.getName().getLocalPart();
				if ("ImageSets".equals(localPart)) {
					return;
				} else if ("SwatchImage".equals(localPart)
						|| "SmallImage".equals(localPart)
						|| "MediumImage".equals(localPart)
						|| "LargeImage".equals(localPart)) {
					size = ImageSize.Others;
				}

			}
		}
	}
	
	private void updateItemAttributes(XMLEventReader r) throws XMLStreamException {
		
		// initialize
		studio = null;
		releaseDate = null;
		publisher = null;
		manufacturer = null;
		label = null;
		actors.clear();
		artists.clear();
		directors.clear();
		formats.clear();
		authors.clear();
		aspectRatio = null;
		eSRBAgeRating = null;
		hardwarePlatform = null;
		operatingSystem = null;
		platform = null;
		numberOfPages = 0;
		productGroup = null;
		publicationDate = null;
		runningTime = 0;
		binding = null;
		title = null;
		
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		String lastElementName = EMPTY;
		while (r.hasNext()) {
			XMLEvent e = r.nextEvent();
			if (e.isStartElement()) {
				StartElement element = (StartElement) e;
				lastElementName = element.getName().getLocalPart();
			} else if (e.isCharacters()) {
				Characters ch = (Characters) e;
				if ("Studio".equals(lastElementName)) {
					studio = ch.getData();
				} else if ("ReleaseDate".equals(lastElementName)) {
					try {
						releaseDate = sdf.parse(ch.getData());
					} catch (ParseException e1) {
					}
				} else if ("Publisher".equals(lastElementName)) {
					publisher = ch.getData();
				} else if ("Manufacturer".equals(lastElementName)) {
					manufacturer = ch.getData();
				} else if ("Label".equals(lastElementName)) {
					label = ch.getData();
				} else if ("Actor".equals(lastElementName)) {
					actors.add(ch.getData());
				} else if ("Artist".equals(lastElementName)) {
					artists.add(ch.getData());
				} else if ("Director".equals(lastElementName)) {
					directors.add(ch.getData());
				} else if ("Format".equals(lastElementName)) {
					formats.add(ch.getData());
				} else if ("Author".equals(lastElementName)) {
					authors.add(ch.getData());
				} else if ("AspectRatio".equals(lastElementName)) {
					aspectRatio = ch.getData();
				} else if ("ESRBAgeRating".equals(lastElementName)) {
					eSRBAgeRating = ch.getData();
				} else if ("HardwarePlatform".equals(lastElementName)) {
					hardwarePlatform = ch.getData();
				} else if ("OperatingSystem".equals(lastElementName)) {
					operatingSystem = ch.getData();
				} else if ("Platform".equals(lastElementName)) {
					platform = ch.getData();
				} else if ("NumberOfPages".equals(lastElementName)) {
					numberOfPages = Integer.parseInt(ch.getData());
				} else if ("ProductGroup".equals(lastElementName)) {
					String group = ch.getData();
					if (group.equals("DVD")) {
						productGroup = "Visual";
					} else if (group.equals("Video Games")) {
						productGroup = "Game";
					} else {
						productGroup = group;
					}
				} else if ("PublicationDate".equals(lastElementName)) {
					try {
						publicationDate = sdf.parse(ch.getData());
					} catch (ParseException e1) {
					}
				} else if ("RunningTime".equals(lastElementName)) {
					runningTime = Integer.parseInt(ch.getData());
				} else if ("Binding".equals(lastElementName)) {
					binding = ch.getData();
				} else if ("Title".equals(lastElementName)) {
					title = ch.getData();
				}
				
				lastElementName = EMPTY;
			} else if (e.isEndElement()) {
				EndElement element = (EndElement)e;
				String localPart = element.getName().getLocalPart();

				if ("ItemAttributes".equals(localPart)) {
					return;
				}
			}
		}
	}
	
	private void updateOfferSummary(XMLEventReader r) throws XMLStreamException {
		lowestNewPrice = 0; 
		lowestUsedPrice = 0;
		
		boolean newPrice = false;
		boolean getAmount = false;
		boolean usedPrice = false;
		
		while (r.hasNext()) {
			XMLEvent e = r.nextEvent();
			if (e.isStartElement()) {
				StartElement element = (StartElement) e;
				String localPart = element.getName().getLocalPart();
				if ("LowestNewPrice".equals(localPart)) {
					newPrice = true;
				} else if ("LowestUsedPrice".equals(localPart)) {
					usedPrice = true;
				} else if ("Amount".equals(localPart)) {
					getAmount = true;
				}
			} else if (e.isCharacters()) {
				if (getAmount) {
					Characters ch = (Characters) e;
					if (newPrice) {
						this.lowestNewPrice = Integer.parseInt(ch.getData());
					} else if (usedPrice) {
						this.lowestUsedPrice = Integer.parseInt(ch.getData());
					}
					getAmount = false;
				}
			} else if (e.isEndElement()) {
				EndElement element = (EndElement)e;
				String localPart = element.getName().getLocalPart();
				if ("OfferSummary".equals(localPart)) {
					return;
				} else if ("LowestNewPrice".equals(localPart)) {
					newPrice = false;
				} else if ("LowestUsedPrice".equals(localPart)) {
					usedPrice = false;
				}

			}
		}
	}
	
	private void updateTracks(XMLEventReader r) throws XMLStreamException {
		tracks.clear();
		
		boolean getTrack = false;
		
		while (r.hasNext()) {
			XMLEvent e = r.nextEvent();
			if (e.isStartElement()) {
				StartElement element = (StartElement) e;
				String localPart = element.getName().getLocalPart();
				if ("Disc".equals(localPart)) {
					// temporary discarding disc information
				} else if ("Track".equals(localPart)) {
					getTrack = true;
				}
			} else if (e.isCharacters()) {
				if (getTrack) {
					Characters ch = (Characters) e;
					tracks.add(ch.getData());
					getTrack = false;
				}
			} else if (e.isEndElement()) {
				EndElement element = (EndElement)e;
				String localPart = element.getName().getLocalPart();
				if ("Tracks".equals(localPart)) {
					return;
				}

			}
		}
	}
	
	public String getReviewTitle() {
		return reviewTitle;
	}
	public void setReviewTitle(String reviewTitle) {
		this.reviewTitle = reviewTitle;
	}
	public String getTitle() {
		return title;
	}
	public String getStudio() {
		return studio;
	}
	public Date getReleaseDate() {
		return releaseDate;
	}
	public String getPublisher() {
		return publisher;
	}
	public String getManufacturer() {
		return manufacturer;
	}
	public String getLabel() {
		return label;
	}
	
	public String getAsin() {
		return asin;
	}

	public List<String> getAuthors() {
		return authors;
	}
	
	public List<String> getActors() {
		return actors;
	}
	public List<String> getArtists() {
		return artists;
	}
	public List<String> getDirectors() {
		return directors;
	}
	public List<String> getFormats() {
		return formats;
	}
	public String getAspectRatio() {
		return aspectRatio;
	}
	public String getBinding() {
		return binding;
	}
	public String geteSRBAgeRating() {
		return eSRBAgeRating;
	}
	public String getHardwarePlatform() {
		return hardwarePlatform;
	}
	public String getOperatingSystem() {
		return operatingSystem;
	}
	public String getPlatform() {
		return platform;
	}
	public int getNumberOfPages() {
		return numberOfPages;
	}
	public String getProductGroup() {
		return productGroup;
	}
	public Date getPublicationDate() {
		return publicationDate;
	}
	public int getRunningTime() {
		return runningTime;
	}
	public int getLowestNewPrice() {
		return lowestNewPrice;
	}
	public int getLowestUsedPrice() {
		return lowestUsedPrice;
	}
	public String getSwatchImageUrl() {
		return swatchImageUrl;
	}
	public String getSmallImageUrl() {
		return smallImageUrl;
	}
	public String getLargeImageUrl() {
		return largeImageUrl;
	}
	public String getMediumImageUrl() {
		return mediumImageUrl;
	}
	public String getDetailPageUrl() {
		return detailPageUrl == null ? null : detailPageUrl.getValue();
	}
	public List<String> getVarientImagesUrl() {
		return varientImagesUrl;
	}
	public Date getCachedDate() {
		return cachedDate;
	}
	public List<String> getTracks() {
		return tracks;
	}
	
	public Date getReviewDate() {
		return reviewDate;
	}
	
	public void setReviewDate(Date reviewDate) {
		this.reviewDate = reviewDate;
		updateYear();
	}

	public void updateYear() {
		Calendar cal = Calendar.getInstance();
		cal.setTime(this.reviewDate);
		
		Calendar now = java.util.Calendar.getInstance();
		this.reviewYear = (short)(now.get(Calendar.YEAR) - cal.get(Calendar.YEAR));
		cal.set(Calendar.YEAR, now.get(Calendar.YEAR));
		if (cal.compareTo(now) > 0) this.reviewYear--;
	}
	
	public String getReview() {
		return review == null ? null : review.getValue();
	}
	public void setReview(String review) {
		this.review = new Text(review);
	}
	public short getScore() {
		return score;
	}
	public void setScore(short score) {
		this.score = score;
		if (score == 0)
			this.numberOfStars = 0;
		else if (score <= 10)
			this.numberOfStars = 1;
		else if (score <= 20)
			this.numberOfStars = 2;
		else if (score <= 30)
			this.numberOfStars = 3;
		else if (score <= 40)
			this.numberOfStars = 4;
		else if (score <= 50)
			this.numberOfStars = 5;
		else if (score <= 60)
			this.numberOfStars = 6;
		else if (score <= 70)
			this.numberOfStars = 7;
		else if (score <= 80)
			this.numberOfStars = 8;
		else if (score <= 90)
			this.numberOfStars = 9;
		else
			this.numberOfStars = 10;
	}
	public String getItemId() {
		return itemId;
	}
	
	public void setAsin(String asin) {
		this.asin = asin;
	}

	public String getStar() {
		
		return exchangeToStar(this.numberOfStars);
	}

	public static String exchangeToStar(short numberOfStars) {
		switch (numberOfStars) {
		case 0:
			return "";
		case 1:
			return "★-";
		case 2:
			return "★";
		case 3:
			return "★★-";
		case 4:
			return "★★";
		case 5:
			return "★★★-";
		case 6:
			return "★★★";
		case 7:
			return "★★★★-";
		case 8:
			return "★★★★";
		case 9:
			return "★★★★★-";
		case 10:
			return "★★★★★";
		default:
			return "";
		}
	}

	public static String getStarWithShadow(short numberOfStars) {
		return (exchangeToStar(numberOfStars) + "☆☆☆☆☆").substring(0, 6);
	}

	public Date getLastModified() {
		return lastModified;
	}

	public void setLastModified(Date lastModified) {
		this.lastModified = lastModified;
	}

	public List<String> getRelatedAsins() {
		return relatedAsins;
	}

	public void setRelatedAsin(List<String> relatedAsins) {
		this.relatedAsins = relatedAsins;
	}

	public short getNumberOfStars() {
		return numberOfStars;
	}

	public short getReviewYear() {
		return reviewYear;
	}

}
